package net.flydy.ywxcx.wxcloudrun.service.impl;

import cn.hutool.core.net.URLEncodeUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.flydy.ywxcx.wxcloudrun.common.*;
import net.flydy.ywxcx.wxcloudrun.config.*;
import net.flydy.ywxcx.wxcloudrun.dao.*;
import net.flydy.ywxcx.wxcloudrun.dto.*;
import net.flydy.ywxcx.wxcloudrun.model.*;
import net.flydy.ywxcx.wxcloudrun.vo.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author flydy
 */
@Slf4j
@Service
public class MerchantService {

    @Autowired
    private MerchantMapper merchantMapper;
    @Autowired
    private MerchantCouponMapper merchantCouponMapper;

    @Autowired
    private ConfigService configService;

    @Autowired
    private MyHttpClient myHttpClient;

    @Autowired
    private MyCacheService myCacheService;


    ////////////////////////////// 业务方法开始 ////////////////////////
    /**
     * @param dto
     */
    @SneakyThrows
    public void wxLogin(BaseDTO<WXLoginReq, BaseResponse<WXLoginRespData>> dto) {
        WXLoginReq request = dto.getRequest();
        String wxCode = request.getWxCode();
        BaseException.checkAndThrow(StringUtils.isNotBlank(wxCode), StatusEnum.VALIDATION_FAIL, null, "wxCode null");

        //https get
        String wxSessionUrl = configService.getEnvConfig("wx.jscode2session.url", "https://api.weixin.qq.com/sns/jscode2session");
        StringBuilder sbParams = new StringBuilder();
        sbParams.append("appid=" + configService.getEnvConfig("wx.appid", "10000"));
        sbParams.append("&secret=" + configService.getEnvConfig("wx.secret", "10000"));
        sbParams.append("&grant_type=" + configService.getEnvConfig("wx.grant_type", "10000"));
        sbParams.append("&js_code=" + wxCode);
        String params = URLEncodeUtil.encode(sbParams.toString());
        wxSessionUrl = wxSessionUrl + "?" + params;
        String wxResp = myHttpClient.get(wxSessionUrl, null);
        JSONObject respM = JSONUtil.parseObj(wxResp);
        int  errcode = respM.getInt("errcode");
        log.error("微信登录异常: js_code="+wxCode, respM);
        BaseException.checkAndThrow(errcode == 0, StatusEnum.WX_LOGIN_ERROR, null, "微信登录异常:"+respM);

        String openid = respM.getStr("openid");
        String session_key = respM.getStr("session_key");
        String unionid = respM.getStr("unionid");
        //生成自己的token
        String wxId = StringUtils.isNotBlank(unionid) ? unionid : openid;
        MyToken myToken = new MyToken();
        String tokenId = UUIDBase58.base58Uuid();
        String tokenIdWithSign = signToken(tokenId);
        myToken.setTokenId(tokenIdWithSign);
        myToken.setWxId(wxId);
        myToken.setWxName(request.getNickName());
        myToken.setLoginIp(dto.getReqIp());
        myToken.setLoginTime(System.currentTimeMillis());
        myToken.setUpdateTime(System.currentTimeMillis());
        myToken.setWxSessionKey(session_key);

        //存储会话到redis
        saveToken(tokenId, myToken);

        BaseResponse baseResponse = BaseResponse.getSuccessInstance();
        WXLoginRespData data = new WXLoginRespData();
        data.setTokenId(myToken.getTokenId());
        data.setWxId(wxId);
        baseResponse.setDataBody(data);
        dto.setResponse(baseResponse);
    }


    public void findMyStore(MerchantDTO<FindMyStoreReq, BaseResponse<MyStoresResp>> dto) {
        FindMyStoreReq req = dto.getRequest();
        MyToken myToken = dto.getMyToken();
        String wxId = myToken.getWxId();
        List<Merchant> list = findByOpenId(wxId);

        MyStoresResp resp = new MyStoresResp();
        resp.setList(list);
        dto.setResponse(BaseResponse.getSuccessInstance(resp));
    }

    public void findUnBindStore(MerchantDTO<FindUnBindStoreReq, BaseResponse<MyStoresResp>> dto) {
        FindUnBindStoreReq req = dto.getRequest();
        String phoneNumber = req.getPhoneNumber();
        String bcode = req.getBcode();
        BaseException.checkAndThrow(StringUtils.isNotBlank(phoneNumber), StatusEnum.VALIDATION_FAIL, null, "phoneNumber null");
        BaseException.checkAndThrow(StringUtils.isNotBlank(bcode), StatusEnum.VALIDATION_FAIL, null, "bcode null");
        List<Merchant> list = findByPhoneNumNotBind(phoneNumber, bcode);

        MyStoresResp resp = new MyStoresResp();
        resp.setList(list);
        dto.setResponse(BaseResponse.getSuccessInstance(resp));
    }

    /**
     * 绑定商店
     * 检查商铺的手机号 + bcode 是否一致
     * @param dto
     */
    public void bindStore(MerchantDTO<BindStoreReq, BaseResponse<BindStoreResp>> dto) {
        BindStoreReq req = dto.getRequest();
        String phoneNumber = req.getPhoneNumber();
        String bcode = req.getBcode();
        String wxId = req.getWxId();
        BaseException.checkAndThrow(StringUtils.isNotBlank(phoneNumber), StatusEnum.VALIDATION_FAIL, null, "phoneNumber null");
        BaseException.checkAndThrow(StringUtils.isNotBlank(bcode), StatusEnum.VALIDATION_FAIL, null, "bcode null");
        BaseException.checkAndThrow(StringUtils.isNotBlank(wxId), StatusEnum.VALIDATION_FAIL, null, "wxId null");
        //token 里面的wxId 是否相等？
        BaseException.checkAndThrow(StringUtils.equals(wxId, dto.getMyToken().getWxId()), StatusEnum.VALIDATION_FAIL, null, "wxId bad");

        Long sid = req.getStoreId();
        BaseException.checkAndThrow(sid != null, StatusEnum.VALIDATION_FAIL, null, "非法请求");
        Merchant merchant = merchantMapper.selectByPrimaryKey(sid);
        BaseException.checkAndThrow(merchant != null, StatusEnum.NO_RESOURCE, null, "店铺不存在");
        BaseException.checkAndThrow(merchant.getStatus() == 1, StatusEnum.NO_RESOURCE, null);

        String dbcode = merchant.getBcode();
        BaseException.checkAndThrow(StringUtils.isBlank(dbcode) || StringUtils.equals(dbcode, bcode), StatusEnum.VALIDATION_FAIL, null, "非法bcode");
        String dbPhone = merchant.getPhoneNum();
        BaseException.checkAndThrow(StringUtils.equals(dbPhone, phoneNumber), StatusEnum.VALIDATION_FAIL, null, "非法手机号");

        String dbwxId = merchant.getWxid();
        BaseException.checkAndThrow(StringUtils.isBlank(dbwxId), StatusEnum.HAS_BIND, null, "商铺已经绑定: "+wxId);

        //绑定wxId
        Merchant bindMer = new Merchant();
        bindMer.setId(sid);
        bindMer.setModifyTime(new Date());
        bindMer.setWxid(wxId);
        bindMer.setWxName(dto.getMyToken().getWxName());
        int r = merchantMapper.updateByPrimaryKeySelective(bindMer);
        BaseException.checkAndThrow( r > 0, StatusEnum.BIND_ERROR, null, "绑定异常，请重试");

        //成功返回
        BindStoreResp resp = new BindStoreResp();
        resp.setMerchant(merchant);
        dto.setResponse(BaseResponse.getSuccessInstance(resp));
    }

    public void unBindStore(MerchantDTO<UnBindStoreReq, BaseResponse<UnBindStoreResp>> dto) {
        UnBindStoreReq request = dto.getRequest();
        Long sid = request.getStoreId();
        String wxId = request.getWxId();
        Merchant find = merchantMapper.selectByPrimaryKey(sid);
        BaseException.checkAndThrow(find != null, StatusEnum.NO_RESOURCE, null);
        BaseException.checkAndThrow(find.getStatus() == 1, StatusEnum.NO_RESOURCE, null);
        BaseException.checkAndThrow(StringUtils.equals(find.getWxid(), wxId), StatusEnum.BAD_OPT, null);

        Merchant update = new Merchant();
        update.setId(sid);
        update.setWxid("");
        update.setWxName("");
        update.setModifyTime(new Date());
        int n = merchantMapper.updateByPrimaryKeySelective(update);
        //成功返回
//        UnBindStoreResp resp = new UnBindStoreResp();
//        resp.setUpc(n);
        dto.setResponse(BaseResponse.getSuccessInstance(null));
    }

    public void findMyCoupons(MerchantDTO<MerchantCouponReq, BaseResponse<MerchantCouponResp>> dto) {
        MerchantCouponReq request = dto.getRequest();
        Long sid = request.getStoreId();
        String wxId = request.getWxId();
        Merchant find = merchantMapper.selectByPrimaryKey(sid);
        BaseException.checkAndThrow(find != null, StatusEnum.NO_RESOURCE, null);
        BaseException.checkAndThrow(find.getStatus() == 1, StatusEnum.NO_RESOURCE, null);
        BaseException.checkAndThrow(StringUtils.equals(find.getWxid(), wxId), StatusEnum.BAD_OPT, null);

        List<MerchantCoupon> list = merchantCouponMapper.selectByMerchant(sid);
        MerchantCouponResp resp = new MerchantCouponResp();
        resp.setList(list);
        dto.setResponse(BaseResponse.getSuccessInstance(resp));
    }

    public void exchangeCoupons(MerchantDTO<MerchantCouponReq, BaseResponse<ExchangeCouponsResp>> dto) {
        MerchantCouponReq request = dto.getRequest();
        Long sid = request.getStoreId();
        String wxId = request.getWxId();
        Merchant find = merchantMapper.selectByPrimaryKey(sid);
        BaseException.checkAndThrow(find != null, StatusEnum.NO_RESOURCE, null);
        BaseException.checkAndThrow(find.getStatus() == 1, StatusEnum.NO_RESOURCE, null);
        BaseException.checkAndThrow(StringUtils.equals(find.getWxid(), wxId), StatusEnum.BAD_OPT, null);

        Long cid = request.getCouponId();
        BaseException.checkAndThrow(cid != null, StatusEnum.NO_RESOURCE, null);
        MerchantCoupon merchantCoupon = merchantCouponMapper.selectByPrimaryKey(cid);
        BaseException.checkAndThrow(merchantCoupon != null, StatusEnum.NO_RESOURCE, null);
        BaseException.checkAndThrow(merchantCoupon.getStatus() != 0, StatusEnum.BAD_OPT, null);
        //兑换码
        String exchangeCode = merchantCoupon.getExchangeCode();
        if (StringUtils.isNotBlank(exchangeCode)) {
            BaseException.checkAndThrow(StringUtils.equals(exchangeCode, request.getExchangeCode()), StatusEnum.BAD_OPT, null);
        }
        //兑换
        MerchantCoupon updater = new MerchantCoupon();
        updater.setId(cid);
        updater.setExchangeTime(new Date());
        updater.setStatus(1);
        merchantCouponMapper.updateByPrimaryKeySelective(updater);

        ExchangeCouponsResp resp = new ExchangeCouponsResp();
        dto.setResponse(BaseResponse.getSuccessInstance(resp));
    }

    ///////////////////////////// 业务方法结束 //////////////////////////

    public String signToken(String tokenId) throws Exception{
        String tokenKey = configService.getEnvConfig("merchant.token.key", "1234567890abcdef");
        String tokenIdWithSign = tokenId +"@"+ UUIDBase58.encode(EncryptUtils.HMAC(UUIDBase58.decode(tokenId), tokenKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        return tokenIdWithSign;
    }
    public boolean checkToken(String tokenId, String sign) throws Exception{
        String tokenKey = configService.getEnvConfig("merchant.token.key", "1234567890abcdef");
        String mysign = UUIDBase58.encode(EncryptUtils.HMAC(UUIDBase58.decode(tokenId), tokenKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        return StringUtils.equals(sign, mysign);
    }

    public void saveToken(String tokenId, MyToken myToken) {
        String key = ("merchant:token:" + tokenId);
        int ttl = configService.getEnvConfig("merchant.token.ttl", 30*60);
        myCacheService.put(key, myToken, ttl, true);
    }

    @Async
    public void asynRefreshToken(String tokenId, MyToken myToken) {
        saveToken(tokenId, myToken);
    }

    public MyToken getAupdateToken(String tokenId) {
        String key = ("merchant:token:" + tokenId);
        MyToken myToken = myCacheService.get(key);
        if (myToken != null) {
            int uTtl = configService.getEnvConfig("merchant.token.updateTtl", 300);
            long utime = myToken.getUpdateTime();
            if (System.currentTimeMillis() - utime <= uTtl * 1000L) {
                myToken.setUpdateTime(System.currentTimeMillis());
                asynRefreshToken(tokenId, myToken);
            }
        }
        return myToken;
    }

    /**
     * 查找可用的商铺信息
     *
     * @param openid
     * @return
     */
    public List<Merchant> findByOpenId(String openid) {
        List<Merchant> list = merchantMapper.selectByOpenId(openid).stream().filter(item -> item.getStatus() == 1).collect(Collectors.toList());
        return list;
    }

    /**
     * 根据手机号查找未绑定的可用的店铺
     *
     * @param phoneNum
     * @return
     */
    public List<Merchant> findByPhoneNumNotBind(String phoneNum, String bcode) {
        List<Merchant> list = merchantMapper.selectByPhoneNumAndBcode(phoneNum, bcode).stream().filter(item ->
                        item.getStatus() == 1
                                && StringUtils.equals(item.getBcode(), bcode)
                                && StringUtils.isBlank(item.getWxid())
                )
                .collect(Collectors.toList());
        return list;
    }

//    @Transactional
//    public Merchant bindOpenId(Long mid, String wxid, String wxName) {
////        Merchant find = merchantMapper.selectByPrimaryKey(mid);
////        BaseException.checkAndThrow(find != null, StatusEnum.NO_RESOURCE, null);
////        BaseException.checkAndThrow(find.getStatus() == 1, StatusEnum.NO_RESOURCE, null);
////        BaseException.checkAndThrow(StringUtils.isBlank(find.getWxid()), StatusEnum.HAS_BIND, null);
//
//        Merchant update = new Merchant();
//        update.setId(mid);
//        update.setWxid(wxid);
//        update.setWxName(wxName);
//        update.setModifyTime(new Date());
//        int n = merchantMapper.updateByPrimaryKeySelective(update);
//
//        return merchantMapper.selectByPrimaryKey(mid);
//    }


}
